package com.coincalf.wallet.blockchain;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.coincalf.wallet.blockchain.bean.trx.TrxTransaction;
import com.coincalf.wallet.blockchain.bean.xrp.XrpAccount;
import com.coincalf.wallet.blockchain.bean.xrp.XrpRequestBean;
import com.coincalf.wallet.blockchain.bean.xrp.XrpTx;
import com.coincalf.wallet.blockchain.bean.xrp.XrpTxs;
import com.coincalf.wallet.blockchain.util.HttpUtil;
import com.coincalf.wallet.blockchain.util.JsonUtils;
import com.ripple.core.coretypes.AccountID;
import com.ripple.core.coretypes.Amount;
import com.ripple.core.coretypes.uint.UInt32;
import com.ripple.core.types.known.tx.signed.SignedTransaction;
import com.ripple.core.types.known.tx.txns.Payment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

/**
 * 瑞波币
 */
public class RippleService {

    //测试网节点
    private static final String testRpcNode = "http://34.210.87.206:51234";
    //主网公共节点 用于查询交易详情
    private static final String mainRpcNode = "http://s2.ripple.com:51234/";
    //公司节点
    private static final String ourRpcNode = "http://xrpuser:xrppass@47.244.160.163:5005";

    private static final Logger log = LoggerFactory.getLogger(RippleService.class);

    /**
     * 生成seed(不安全)
     * xrp导入seed获取账户 相当于其他钱包的私钥
     *
     * @param secret 种子生成凭据(不同用户必须保证凭据唯一,否则账户会重复)
     *               和seed以及助记词一一对应
     *               此处如果输入助记词或seed,结果是一样的
     * @return result
     * @throws IOException
     */
    public static Map<String, String> getSeed(String secret) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "validation_create");
        List<Map<String, String>> params = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("secret", secret);
        params.add(map);
        param.put("params", params);
        String response = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject result = JSONObject.parseObject(response).getJSONObject("result");
        String seed = result.getString("validation_seed");
        String mnemonics = result.getString("validation_key");
        String privateKey = result.getString("validation_private_key");
        String publicKey = result.getString("validation_public_key");
        map.clear();
        map.put("seed", seed);
        map.put("mnemonics", mnemonics);    //12个助记词
        map.put("privateKey", privateKey);
        map.put("publicKey", publicKey);
        return map;
    }

    /**
     * 生成随机seed
     *
     * @return
     * @throws IOException
     */
    public static Map<String, Object> getSeed() throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "validation_create");
        String response = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject result = JSONObject.parseObject(response).getJSONObject("result");
        String seed = result.getString("validation_seed");
        String mnemonics = result.getString("validation_key");
        String privateKey = result.getString("validation_private_key");
        String publicKey = result.getString("validation_public_key");
        param.clear();
        param.put("seed", seed);
        param.put("mnemonics", mnemonics);    //12个助记词
        param.put("privateKey", privateKey);
        param.put("publicKey", publicKey);
        return param;
    }

    /**
     * 查询账户信息
     *
     * @param address 钱包地址
     * @return 地址信息
     * @throws IOException
     */
    public static XrpAccount getAccountInfo(String address) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "account_info");
        List<XrpRequestBean> params = new ArrayList<>();
        XrpRequestBean bean = new XrpRequestBean();
        bean.setAccount(address);
        params.add(bean);
        param.put("params", params);
        String result = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject jsonObject = JSONObject.parseObject(result).getJSONObject("result");
        String status = jsonObject.getString("status");
        if (Objects.equals("success", status)) {
            String accountData = jsonObject.getString("account_data");
            return JSON.parseObject(accountData, XrpAccount.class);
        } else {
            Integer errorCode = jsonObject.getInteger("error_code");
            if (errorCode == 35) {
                throw new RuntimeException("INCORRECT ADDRESS FORMAT");
            }
            if (errorCode == 19) {
                throw new RuntimeException("ADDRESS NOT ACTIVATED");
            }
            throw new RuntimeException(jsonObject.getString("error"));
        }
    }

    /**
     * 根据交易hash获取交易详情
     *
     * @param hash 交易哈希
     * @return
     */
    public static XrpTx getTx(String hash) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "tx");
        List<Map<String, Object>> params = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("transaction", hash);
        params.add(map);
        param.put("params", params);
        String result = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject txResult = JSONObject.parseObject(result).getJSONObject("result");
        String status = txResult.getString("status");
        if (Objects.equals(status, "success")) {
            return JSON.parseObject(txResult.toString(), XrpTx.class);
        } else {
            String error = txResult.getString("error");
            if (Objects.equals(error, "notImpl")) {
                log.error("XRP交易哈希错误 hash={}", hash);
            }
            if (Objects.equals(error, "txnNotFound")) {
                log.error("没有找到该XRP交易 交易失败~ hash={}, error={}", hash, error);
            }
        }
        return null;
    }

    /**
     * 获取地址某两个区块之间的交易纪录[startBlock, endBlock]
     * 官方文档：https://xrpl.org/account_tx.html
     *
     * @param address    钱包地址
     * @param startBlock 查询起始区块高度 -1表示所有
     * @param endBlock   查询结束区块高度 -1表示所有
     * @return 地址所有交易记录
     */
    public static List<XrpTxs.Transactions> getTxs(String address, long startBlock, long endBlock) {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "account_tx");
        List<Map<String, Object>> params = new ArrayList<>();
        Map<String, Object> map = new HashMap<>();
        map.put("account", address);
        map.put("ledger_index_min", startBlock); //起始区块高度
        map.put("ledger_index_max", endBlock);   //结束区块高度
        map.put("forward", true);  //交易纪录正序还是倒叙 false表示倒序 true为正序 按交易时间排序
        //map.put("limit", limit);                  //本次查询交易数量
        //map.put("ledger_index", blockHeight);    //区块高度 加上则为查询具体某个区块高度的交易纪录
        params.add(map);
        param.put("params", params);
        String result = HttpUtil.doPost(mainRpcNode, JsonUtils.writeValueAsString(param));
        System.out.println(result);
        JSONObject response = JSONObject.parseObject(result).getJSONObject("result");
        XrpTxs xrpTxs = JsonUtils.convertValue(response, XrpTxs.class);
        String status = xrpTxs.getStatus();
        if (Objects.equals(status, "success")) {
            return xrpTxs.getTransactions();
        } else {
            //status为error 说明没有查找到数据
            log.error("XRP历史交易记录查询失败");
            return new ArrayList<>();
        }
    }

    /**
     * 通过seed创建xrp地址 算法:secp256k1
     *
     * @return xrp地址
     */
    public static Map<String, String> getNewAddress(String seed) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "wallet_propose");
        List<Map<String, String>> params = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("seed", seed);
        map.put("key_type", "secp256k1");
        params.add(map);
        param.put("params", params);
        String result = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject response = JSONObject.parseObject(result).getJSONObject("result");
        String account = response.getString("account_id");
        String seedHex = response.getString("master_seed_hex");
        String publicKey = response.getString("public_key");
        String publicKeyHex = response.getString("public_key_hex");
        map.clear();
        map.put("account", account);
        map.put("seedHex", seedHex);    //16进制seed
        map.put("publicKey", publicKey);
        map.put("publicKeyHex", publicKeyHex);
        return map;
    }

    /**
     * 通过seed生成xrp地址 算法:ed25119
     *
     * @param seed
     * @return
     * @throws IOException
     */
    public static Map<String, String> getNewAddressByEd25119(String seed) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "wallet_propose");
        List<Map<String, String>> params = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("seed", seed);
        map.put("key_type", "ed25119");
        params.add(map);
        param.put("params", params);
        String result = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        JSONObject response = JSONObject.parseObject(result).getJSONObject("result");
        String account = response.getString("account_id");
        String seedHex = response.getString("master_seed_hex");
        String publicKey = response.getString("public_key");
        String publicKeyHex = response.getString("public_key_hex");
        map.clear();
        map.put("account", account);
        map.put("seedHex", seedHex);    //16进制seed
        map.put("publicKey", publicKey);
        map.put("publicKeyHex", publicKeyHex);
        return map;
    }

    /**
     * 新建xrp账户地址
     *
     * @param secret
     * @return
     * @throws IOException
     */
    public static String getNewAccount(String secret) throws IOException {
        Map<String, String> seedInfo = getSeed(secret);
        String seed = seedInfo.get("seed");
        Map<String, String> accountInfo = getNewAddress(seed);
        return accountInfo.get("account");
    }

    /**
     * 生成随机xrp账户地址
     *
     * @return
     * @throws IOException
     */
    public static String getNewAccount() throws IOException {
        Map<String, Object> randomSeedInfo = getSeed();
        String randomSeed = randomSeedInfo.get("seed").toString();
        Map<String, String> accountInfo = getNewAddress(randomSeed);
        return accountInfo.get("account");
    }

    /**
     * 签名
     *
     * @return tx_blob
     */
    public static String sign(Long tag, String fromAddress, String secret, String toAddress, BigDecimal value, long fee) throws IOException {
        XrpAccount account = getAccountInfo(fromAddress);
        long sequence = account.getSequence();
        Payment payment = new Payment();
        payment.as(AccountID.Account, fromAddress);
        payment.as(AccountID.Destination, toAddress);
        payment.amount(new Amount(value));
        payment.as(UInt32.Sequence, sequence);
        if (tag == null || tag == 0) {
            payment.as(UInt32.DestinationTag, tag);
        } else {
            payment.as(UInt32.DestinationTag, "1");
        }
        payment.as(Amount.Fee, Long.toString(fee));
        SignedTransaction signed = payment.sign(secret);
        return signed.tx_blob;
    }

    /**
     * 发送交易
     *
     * @param fromAddress 转账地址
     * @param secret      转账方密钥
     * @param toAddress   收款地址
     * @param tag         标记
     * @param value       转账金额
     * @param fee         交易费
     * @return 交易结果
     * @throws IOException
     */
    public static String submit(String fromAddress, String secret, String toAddress, Long tag, BigDecimal value, long fee) throws Exception {
        log.info("XRP交易参数 -> from={}, to={}, secret={}, tag={}, value={}, fee={}", fromAddress, toAddress, secret, tag, value, fee);
        //签名交易
        String txBlob = sign(tag, fromAddress, secret, toAddress, value, fee);
        log.info("XRP交易签名结果={}", txBlob);
        //广播交易
        return submit(txBlob);
    }

    /**
     * 广播交易
     *
     * @param signedHex 已签名交易的十六进制字符串
     * @return 交易结果
     */
    public static String submit(String signedHex) throws IOException {
        Map<String, Object> param = new HashMap<>();
        param.put("method", "submit");
        List<Map<String, String>> params = new ArrayList<>();
        Map<String, String> map = new HashMap<>();
        map.put("tx_blob", signedHex);
        params.add(map);
        param.put("params", params);
        String result = HttpUtil.doPost(ourRpcNode, JsonUtils.writeValueAsString(param));
        System.out.println(result);
        JSONObject response = JSONObject.parseObject(result).getJSONObject("result");
        String engineResult = response.getString("engine_result");
        if (engineResult != null && Objects.equals(engineResult, "tesSUCCESS")) {
            //交易成功 返回交易哈希
            return response.getJSONObject("tx_json").getString("hash");
        } else {
            String errorReason = response.getString("engine_result_message");
            log.error("XRP交易广播失败 signedHex={}, errorCode={}, ", signedHex, response.getInteger("error_code"));
            log.error("XRP交易失败原因={}", errorReason);
            throw new RuntimeException(response.getString("error_message"));
        }
    }

    public static void main(String[] args) throws Exception {

        String from = "rsZSyGCEqKAyAwWrYNujAooVP2rmYjtaAa";     //测试地址01
        String fromSeed = "ssaijR4aHMaj9EDBdYt9Kdz9J3T2X";
        String to = "rM2c1Qnzpb98RKwV6ykxWNw2tUGnkgyC3u";       //测试地址02
        String toSeed = "snv4PR1pRrfmQxbB6aMrMNFTpzbdF";
        BigDecimal amount = BigDecimal.valueOf(15L);

        //主网地址 和测试网不兼容
        String address = "rPxtKbgqzrYWLF3H3jUmKzPjDASck4XvCJ";
        String pubKey = "02d0b0135b41ca753fc04b433924757b9daa0739227309aaa363e8d6a8526d45c2";
        String privateKey = "4633f55919d7a277959159191341cf6afa11a73fd693bbcf9c40a5d9879e2060";

//        XrpAccount account = getAccountInfo(from);
//        System.out.println(account.getBalance().divide(BigDecimal.valueOf(1000000L), 6, RoundingMode.HALF_DOWN));
//
//        String submit = submit(from, fromSeed, to, 1L, amount, 1000L);
//        System.out.println(submit);

        /*try {
            List<String> s = new ArrayList<>();
            s.add("BAFF09E6B0B772622849C09DFF2A3945C4898BE2F8CA14F88D65FC100BEA7AD0");
            s.add("4AF3C6357B5C9022A7174A576340329FA1728A25A150ACBA39345E489F42A88D");
            s.add("B61910BA411DBEE04D84562682E7A8A6851FB980A246598F074FDCEB7E9A31B6");
            s.add("BAFF09E6B0B772622849C09DFF2A3945C4898BE2F8CA14F88D65FC100BEA7AD0");
            s.add("1D85AE8BDED8B12EA8FAA938F25797256235F05A3A98F1F01658F519A3E0452C");
            for (int i = 0; i < s.size(); i++) {
                String s1 = s.get(i);
                XrpTx ss = getTx(s1);
                System.out.println(JsonUtils.writeValueAsString(ss));
            }
        } catch (IOException e) {
            System.out.println("444444444");
            e.printStackTrace();
        }*/

        getTxs("rcDnLdUaDJPLXiQ6aLVShxDHcqaYkGUcX", 46796557L, -1);

    }

}
